package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.app.mess.AggBehaviorDTO;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.constants.article.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.utils.common.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author mrchen
 * @date 2022/1/11 9:51
 */
@Service
@Slf4j
public class HotArticleServiceImpl implements HotArticleService {

    @Autowired
    ApArticleMapper apArticleMapper;

    @Autowired
    RabbitTemplate rabbitTemplate;

    // RuntimeExecpiton.class
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void computeHotArticle() {
        // 1. 查询出近5天的热点文章数据
        // 1.1    查询出5天前的时间
        String dayParam = LocalDateTime.now().minusDays(5)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        List<ApArticle> articleList = apArticleMapper.selectArticleByDate(dayParam);
        // 1.2    调用articleMapper根据时间查询

        // 2. 计算每篇文章的热度值   VoList
        List<HotArticleVo> hotArticleVoList = getHotArticleVoList(articleList);
        // 3. 按照频道缓存热点文章数据
        cacheToRedisByTag(hotArticleVoList);
    }

    @Override
    public void updateApArticle(AggBehaviorDTO aggBehavior) {
        // 1. 根据文章id 查询出文章数据
        ApArticle article = apArticleMapper.selectById(aggBehavior.getArticleId());
        if(article == null){
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");
        }
        // 2. 根据聚合结果  更新文章各行为参数
        article.setViews((int)(article.getViews()==null?aggBehavior.getView(): article.getViews() + aggBehavior.getView()));
        article.setLikes((int)(article.getLikes()==null?aggBehavior.getLike(): article.getLikes() + aggBehavior.getLike()));
        article.setCollection((int)(article.getCollection()==null?aggBehavior.getCollect(): article.getCollection() + aggBehavior.getCollect()));
        article.setComment((int)(article.getComment()==null?aggBehavior.getComment(): article.getComment() + aggBehavior.getComment()));
        // 3. 修改数据库中文章行为数量
        apArticleMapper.updateById(article);
        // 3. 重新计算该文章得分
        Integer score = computeScore(article);
        // 4. 判断文章是否当日发布， 如果是  热度 * 3
        String nowStr = DateUtils.dateToString(new Date());
        String publishStr = DateUtils.dateToString(article.getPublishTime());
        if(nowStr.equals(publishStr)){
            score = score * 3;
        }
        // 5. 更新 所在频道热点文章缓存
        updateArticleCache(article,score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE+article.getChannelId());
        // 6. 更新 推荐频道热点文章缓存
        updateArticleCache(article,score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE+ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 根据 最新文章热度得分  更新redis缓存
     * @param article
     * @param score
     * @param cacheKey
     */
    private void updateArticleCache(ApArticle article, Integer score, String cacheKey) {
        // 1. 根据缓存的key 查询出 热点文章数据
        String hotArticleJson = redisTemplate.opsForValue().get(cacheKey);
        if(StringUtils.isBlank(hotArticleJson)){
            log.error("热点文章 缓存为空");
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        List<HotArticleVo> hotArticleVoList = JSON.parseArray(hotArticleJson, HotArticleVo.class);
        // 2. 判断当前文章是否在热点文章列表中
        boolean isHas = false;
        for (HotArticleVo articleVo : hotArticleVoList) {
            // 如果集合中 包含当前文章
            if(articleVo.getId().equals(article.getId())){
                // 2.1  如果在  直接更新对应热度分值
                articleVo.setScore(score);
                isHas = true;
                break;
            }
        }
        // 3. 如果不在 直接将文章加入到热点文章列表中
        if(!isHas){
            HotArticleVo vo = new HotArticleVo();
            BeanUtils.copyProperties(article,vo);
            vo.setScore(score);
            hotArticleVoList.add(vo);
        }
        // 4. 将文章集合 按照得分降序排序， 截取前30条热点文章
        hotArticleVoList = hotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        // 5. 重新将热点文章集合 保存到redis
        redisTemplate.opsForValue().set(cacheKey,JSON.toJSONString(hotArticleVoList));
        redisTemplate.expire(cacheKey,2,TimeUnit.DAYS);
    }
    @Autowired
    AdminFeign adminFeign;

    /**
     * 按照频道 将热点文章缓存
     * @param hotArticleVoList
     */
    private void cacheToRedisByTag(List<HotArticleVo> hotArticleVoList) {
        // 1. 远程查询频道列表
        ResponseResult<List<AdChannel>> result = adminFeign.selectChannels();
        //检查远程查询结果
        if(!result.checkCode()){
            log.error(" 远程查询频道列表失败 ");
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR,"远程查询频道列表失败");
        }
        //从远程调用结果集中获取数据
        List<AdChannel> channelList = result.getData();
        if(CollectionUtils.isEmpty(channelList)){
            log.error(" 未查询到相关频道数据 ");
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST,"未查询到相关频道数据");
        }
        // 2. 遍历频道  找出每个频道的文章  进行缓存
        for (AdChannel channel : channelList) {
            // 500  30  存入redis
            List<HotArticleVo> articleVoByChannel = hotArticleVoList.stream()
                    .filter(articleVo -> articleVo.getChannelId().equals(channel.getId()))
                    .collect(Collectors.toList());
            // 将集合缓存
            sortAndCache(articleVoByChannel,ArticleConstants.HOT_ARTICLE_FIRST_PAGE+channel.getId());
        }
        // 3. 推荐频道缓存
        sortAndCache(hotArticleVoList,ArticleConstants.HOT_ARTICLE_FIRST_PAGE+ArticleConstants.DEFAULT_TAG);
    }

    @Autowired
    StringRedisTemplate redisTemplate;


    /**
     * 将文章 按照热度排序  降序
     * 截取 前 30条文章
     * 存入redis
     * @param articleVoList
     * @param cacheKey
     */
    private void sortAndCache(List<HotArticleVo> articleVoList, String cacheKey) {
        //     * 将文章 按照热度排序  降序
//                * 截取 前 30条文章
        List<HotArticleVo> hotArticleList = articleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30).collect(Collectors.toList());
//                * 存入redis
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleList));
        redisTemplate.expire(cacheKey,2, TimeUnit.DAYS);
    }

    /**
     * 计算每篇文章的分支  将article ==> articleVo
     * @param articleList
     * @return
     */
    private List<HotArticleVo> getHotArticleVoList(List<ApArticle> articleList) {
        return articleList.stream().map(apArticle -> {
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(apArticle,hotArticleVo);
            Integer score = computeScore(apArticle);
            hotArticleVo.setScore(score);
            return hotArticleVo;
        }).collect(Collectors.toList());
    }

    private Integer computeScore(ApArticle apArticle) {
        int score = 0;
        if (apArticle.getViews() !=null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if (apArticle.getLikes() !=null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (apArticle.getComment() !=null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (apArticle.getCollection() !=null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }
}